/*
 *
 * Copyright 2015 gRPC authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

// Package main implements a client for Greeter service.
package main

import (
	"context"
	"fmt"
	"log"
	"sync"
	"time"

	"github.com/spf13/pflag"
	"github.com/spf13/viper"
	"go.uber.org/atomic"
	"google.golang.org/grpc"
	pb "google.golang.org/grpc/examples/helloworld/helloworld"
)

const (
	address     = "localhost:50051"
	defaultName = "world"
)

type GreeterClient = pb.GreeterClient

func init() {
	pflag.IntP("goroutines", "g", 1000, "goroutines count")
	pflag.DurationP("time", "m", 15*time.Second, "test time")
	pflag.StringP("address", "a", "localhost:50051", "server address")
}

func main() {
	pflag.Parse()
	viper.BindPFlags(pflag.CommandLine)

	// Set up a connection to the server.
	address := viper.GetString("address")
	conn, err := grpc.Dial(address, grpc.WithInsecure(), grpc.WithBlock())
	if err != nil {
		log.Fatalf("did not connect: %v", err)
	}
	defer conn.Close()
	c := pb.NewGreeterClient(conn)

	tm := viper.GetDuration("time")
	ctx, cancel := context.WithTimeout(context.Background(), tm)
	defer cancel()

	var wg sync.WaitGroup
	var transactions atomic.Uint32
	goroutines := viper.GetInt("goroutines")
	for i := 0; i < goroutines; i++ {
		wg.Add(1)
		go runStressTest(ctx, &wg, &transactions, c)
	}
	wg.Wait()

	fmt.Printf("TPS: %d / %f = %f (t/s)\n", transactions.Load(), tm.Seconds(),
		float64(transactions.Load())/tm.Seconds())
}

func runStressTest(ctx context.Context, wg *sync.WaitGroup, transactions *atomic.Uint32, c GreeterClient) {
	defer wg.Done()

	runs := runStressTest2(ctx, c)
	transactions.Add(runs)
}

func runStressTest2(ctx context.Context, c GreeterClient) uint32 {
	i := uint32(0)
	for {
		select {
		case <-ctx.Done():
			return i
		default:
			request(c)
			i++
		}
	}
}

func request(c GreeterClient) {
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()

	_, err := c.SayHello(ctx, &pb.HelloRequest{Name: "name"})
	if err != nil {
		panic(err)
	}
}
